/*  This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2.1 of the License, or (at
    your option) any later version.
    For more details, see the GNU Lesser General Public License (www.fsf.org
    or the COPYING file somewhere in the package)
 */

#include "ExtensionPointService.hh"

/*! @file src/OSGi/ExtensionPointService.cc
    @brief Class OSGi::ExtensionPointService (non-inline) methods.
    @author @ref Guillaume_Terrissol
    @date 23rd January 2010 - 22nd January 2014
    @note This file is distributed under the LGPL license.
    Refer to the file COPYING (or http://www.fsf.org) for more information.
 */

#include <map>
#include <typeinfo>

#include "BundleMgr.hh"
#include "Exception.hh"
#include "tinyxml.h"

namespace OSGi
{
    /// @cond DEVELOPMENT

//------------------------------------------------------------------------------
//                Extension Point Service Private Implementation
//------------------------------------------------------------------------------

    /*! @brief ExtensionPointService private implementation.
        @version 0.5
     */
    class ExtensionPointService::Private
    {
    public:

        using XPFunc = void (ExtensionPoint::*)(Bundle::CPtr, const XmlElement*);               //!< Extension handling method.

                            Private();                                                          //!< Default constructor.

        ExtensionPoint::Ptr findExtensionPoint(const std::string& pId);                         //!< Looking for an extension point.
        void                parseExtensions(Bundle::Ptr pBndl, XPFunc pFunc);                   //!< Iteration on extensions.

        //                         Name/Id                XP owner      XP itself
        using Container = std::map<std::string, std::pair<Bundle::CPtr, ExtensionPoint::Ptr>>;  //!< ExtensionPoint container.
        Container  mExtensionPoints;                                                            //!< Registered extension points.
    };


//------------------------------------------------------------------------------
//                                P-impl Methods
//------------------------------------------------------------------------------

    /*! Defaulted.
     */
    ExtensionPointService::Private::Private() = default;


    /*! @param pId Name of the searched ExtensionPoint
     */
    ExtensionPoint::Ptr ExtensionPointService::Private::findExtensionPoint(const std::string& pId)
    {
        auto    lExtension = mExtensionPoints.find(pId);

        if (lExtension != end(mExtensionPoints))
        {
            return lExtension->second.second;
        }
        else
        {
            return {};
        }
    }


    /*! @param pBndl Bundle the extensions of which to parse
        @param pFunc ExtensionPoint method to invoke on every found @ref
               OSGi_Extension_Page "extension" (handle / remove)
     */
    void ExtensionPointService::Private::parseExtensions(Bundle::Ptr pBndl, XPFunc pFunc)
    {
        auto    lExtensions = pBndl->resource("extensions.xml");
        if (lExtensions.use_count() != 0)
        {
            TiXmlDocument   lDoc;
            *lExtensions >> lDoc;
            auto*           lRoot = lDoc.RootElement();

            if (lRoot->ValueStr() == "extensions")
            {
                for(auto lExtension = lRoot->FirstChildElement(); lExtension != nullptr; lExtension = lExtension->NextSiblingElement())
                {
                    if (const char* lId = lExtension->Attribute("point"))
                    {
                        auto    lXP = findExtensionPoint(lId);
                        if (lXP.use_count() != 0)
                        {
                            (*lXP.*pFunc)(pBndl, lExtension);
                        }
                    }
                }
            }
        }
    }

    /// @endcond

//------------------------------------------------------------------------------
//              Extension Point Service : Constructor & Destructor
//------------------------------------------------------------------------------

    /*! Defaulted.
     */
    ExtensionPointService::ExtensionPointService() = default;


    /*! Defaulted.
     */
    ExtensionPointService::~ExtensionPointService() = default;


//------------------------------------------------------------------------------
//                      Extension Point Service : Interface
//------------------------------------------------------------------------------

    /*! @return The ExtensionPointService type name
     */
    std::string ExtensionPointService::staticName()
    {
        return typeid(ExtensionPointService).name();
    }


    /*! @param pBundle Bundle providing an ExtensionPoint
        @param pId     Name to register the ExtensionPoint under
        @param pXP     The ExtensionPoint itself
        @note This method shall be called in Activator::doStart implementation
     */
    void ExtensionPointService::checkIn(Bundle::CPtr pBundle, const std::string& pId, ExtensionPoint::Ptr pXP)
    {
        if (pthis->mExtensionPoints.find(pId) == end(pthis->mExtensionPoints))
        {
            pthis->mExtensionPoints[pId] = std::make_pair(pBundle, pXP);
        }
        else
        {
            throw LogicError{"Already defined key : " + pId};
        }
    }


    /*! @param pId Name of the ExtensionPoint to unregister
        @note This method shall be called in Activator::doStop implementation
     */
    void ExtensionPointService::checkOut(const std::string& pId)
    {
        pthis->mExtensionPoints.erase(pId);
    }


//------------------------------------------------------------------------------
//                        Extension Point Service : Slots
//------------------------------------------------------------------------------

    /*! @param pStarted If this just started bundle provides @ref
        OSGi_Extension_Page "extensions", the Service will "add" them to their
        respective ExtensionPoint
     */
    void ExtensionPointService::onBundleStarted(Bundle::Ptr pStarted)
    {
        pthis->parseExtensions(pStarted, &ExtensionPoint::handleExtension);
    }


    /*! @param pStopped If this just started bundle provides @ref
        OSGi_Extension_Page "extensions", the Service will be "remove" them from
        their respective ExtensionPoint
     */
    void ExtensionPointService::onBundleStopped(Bundle::Ptr pStopped)
    {
        pthis->parseExtensions(pStopped, &ExtensionPoint::removeExtension);
    }


//------------------------------------------------------------------------------
//                    Extension Point Service : Implentation
//------------------------------------------------------------------------------

    /*! @copydetails Service::isA(const std::string& pTypeName) const
     */
    bool ExtensionPointService::isType(const std::string& pTypeName) const
    {
        return (staticName() == pTypeName) || Service::isType(pTypeName);
    }


    /*! @copydetails Service::typeName() const
     */
    std::string ExtensionPointService::typeName() const
    {
        return staticName();
    }


//------------------------------------------------------------------------------
//                           Additionnal Documentation
//------------------------------------------------------------------------------

    /*! @class ExtensionPointService
        This service is dedicated to ExtensionPoint management. It is registered
        under the name @ref OSGi_Core_XP.@n
        When a Bundle is started, an @ref OSGi_Extensions_xml_Page file is
        looked for in its data. If present, the file is loaded and, for every
        found named @ref OSGi_Extension_Page "extension", this service searches
        whether an ExtensionPoint has been registered with such a name. If so,
        the service makes that ExtensionPoint handle the found extension (see
        ExtensionPoint::handleExtension(Bundle::CPtr, const XmlElement*)).
     */


     /*! @page OSGi_Extensions_xml_Page 'extensions.xml'
        In order for a Bundle to provide logic or data to an ExtensionPoint (of
        another Bundle), it must embed a file called @b extensions.xml in its
        root folder.@n
        Such a file looks like :@code<extensions>
  <extension point="xp_name">
    <text>Hello world</text>
    <!-- Any other pieces of information the ExtensionPoint "xp_name" expects -->
  </extension>
  <!-- Any other needed extension definitions -->
</extensions>@endcode In this example, the Bundle will give the ExtensionPoint
        (registered under the name @e "xp_name") the @b text @e "Hello world".
        @note The only mandatory XML element in @b @<extension@> is the @b point
        attribute, set with the target extension point name
     */
}
